#include "Defs.h"
#include "Extension.h"
#include "Utils.h"
#include "Context.h"


void WINAPI ReadCompleted(LPEXTENSION_CONTROL_BLOCK pECB, void * pvContext, DWORD dwIO, DWORD dwErr);


void ReadAsync(EXTENSION_CONTROL_BLOCK * pECB, Context_T * contextPtr, DWORD dwAmmount)
{
	//
	// Associate the completion routine with this request
	//
	
	if( !pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_IO_COMPLETION, ReadCompleted, NULL, (LPDWORD)contextPtr) )
	{
		throw Exception_T(GetLastError(), "Error %d ServerSupportFunction(HSE_REQ_IO_COMPLETION) - %s - %d - %s", GetLastError(), __FILE__, __LINE__, __FUNCSIG__);
	}

	//
	// Now that everything is set up, read the remaining
	// payload asynchronously.
	//
	
	DWORD dwFlags = HSE_IO_ASYNC;
	if( !pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_ASYNC_READ_CLIENT, contextPtr->pbEntity, &dwAmmount, &dwFlags) )
	{
		throw Exception_T(GetLastError(), "Error %d ServerSupportFunction(HSE_REQ_ASYNC_READ_CLIENT) - %s - %d - %s", GetLastError(), __FILE__, __LINE__, __FUNCSIG__);
	}
}

BOOL OnGetExtensionVersion(HSE_VERSION_INFO* pVer)
{
	pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
	return TRUE;
}

void WINAPI ExecUrl(LPEXTENSION_CONTROL_BLOCK pECB, void * /*pv*/, DWORD /* dwIO*/, DWORD /*dwErr*/)
{
	pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_DONE_WITH_SESSION, NULL, NULL, NULL);
}

DWORD SendToIis(LPEXTENSION_CONTROL_BLOCK pECB, Context_T * contextPtr)
{
	DWORD dwErr;

	//
	// Fill up HSE_EXEC_URL_INFO with default values
	//

	contextPtr->urlInfo.pszUrl = NULL;
	contextPtr->urlInfo.pszMethod = NULL;
	contextPtr->urlInfo.pszChildHeaders = NULL;
	contextPtr->urlInfo.pUserInfo = NULL;
	contextPtr->urlInfo.dwExecUrlFlags = HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR;
	contextPtr->urlInfo.pEntity = NULL;

	if( contextPtr->rawEntity.Length() > 0 )
	{
		contextPtr->entityInfo.lpbData = contextPtr->rawEntity.Ptr();
		contextPtr->entityInfo.cbAvailable = contextPtr->rawEntity.Length();
		contextPtr->urlInfo.pEntity = &contextPtr->entityInfo;
	}

	//
	// Associate the completion routine with this request
	//
	
	if( !pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_IO_COMPLETION, ExecUrl, NULL, NULL) )
	{
		dwErr = GetLastError();
		goto FREE_MEM;
	}

	//
	// Now that everything is set up, call the child request.
	//

	if( !pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_EXEC_URL, &contextPtr->urlInfo, NULL, NULL) )
	{
		dwErr = GetLastError();
		goto FREE_MEM;
	}

	return HSE_STATUS_PENDING;

FREE_MEM:

	Trace_T::Write(dwErr, __FILE__, __LINE__, __FUNCSIG__);
	SetLastError(dwErr);
	return HSE_STATUS_ERROR;
}

void WINAPI ReadCompleted(LPEXTENSION_CONTROL_BLOCK pECB, void * pvContext, DWORD dwIO, DWORD dwErr)
{
	Context_T * contextPtr = (Context_T *)pvContext;
	Error_T::Code_T code = Error_T::ERR_WINDOWS;

	try
	{
		if( dwErr != NO_ERROR )
		{
			goto FREE_MEM;
		}

		if( dwIO == 0 )
		{
			dwErr = GetLastError();
			goto FREE_MEM;
		}

		//
		// Filter the incomming data
		//
		
		code = contextPtr->rulesPtr->FilterEntityData(Buffer_T(contextPtr->pbEntity, dwIO));
		if( Error_T::ERR_NONE != code )
		{
			goto FREE_MEM;
		}

		//
		// Update the payload read until now
		//
		
		contextPtr->rawEntity.Concat(Buffer_T(contextPtr->pbEntity, dwIO));

		//
		// Deliver to IIS if we done reading
		//
		
		if( pECB->cbTotalBytes == contextPtr->rawEntity.Length() )
		{
			goto SEND_IIS;
		}

		//
		// Read the rest of the payload asynchronously
		//

		ReadAsync(pECB, contextPtr, 
			pECB->cbTotalBytes > sizeof(contextPtr->pbEntity) ? sizeof(contextPtr->pbEntity) : pECB->cbTotalBytes);
	}
	catch( Exception_T & exp )
	{
		dwErr = exp.Error;
		goto FREE_MEM;
	}

	return;

FREE_MEM:

	contextPtr->rulesPtr->Log(contextPtr->session, contextPtr->request, Error_T(code, dwErr));
	if( lstrlen(contextPtr->rulesPtr->szRejectFilename) > 0 )
	{
		contextPtr->Send404(pECB);
		DWORD dwKeepConn = HSE_STATUS_SUCCESS_AND_KEEP_CONN;
		pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_DONE_WITH_SESSION, &dwKeepConn, NULL, NULL);
	}

	if( Error_T::ERR_WINDOWS == code )
	{
		Trace_T::Write(dwErr, __FILE__, __LINE__, __FUNCSIG__);
		SetLastError(dwErr);
		pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_ABORTIVE_CLOSE, NULL, NULL, NULL);
	}

SEND_IIS:

	SendToIis(pECB, contextPtr);
}

DWORD OnHttpExtensionProc(EXTENSION_CONTROL_BLOCK * pECB)
{
	Context_T * contextPtr = 0;
	DWORD dwErr = NO_ERROR;
	Error_T::Code_T code = Error_T::ERR_WINDOWS;
	

	try
	{
		//
		// Get the HF_CTX header.
		// This HTTP header was set by the filter so we can
		// access the location of the context structure.
		//

		String_T contextAddr;
		Utils_T::GetServerVariable(pECB, "HTTP_HF_CTX", contextAddr);
		contextPtr = (Context_T *)(ULONG_PTR)atoi(contextAddr.Ptr());

		//
		// Deliver to IIS if there is no entity to filter
		//
		
		if( pECB->cbTotalBytes == 0 )
		{
			goto SEND_IIS;
		}

		//
		// Check if the total size expected is acceptable
		//
		
		code = contextPtr->rulesPtr->FilterEntitySize(pECB->cbTotalBytes);
		if( Error_T::ERR_NONE != code )
		{
			goto FREE_MEM;
		}

		//
		// check if the entity data needs to be 
		// filtered by the parser
		//

		if( contextPtr->rulesPtr->allowHighBitInPayloadRule.flag )
		{
			goto SEND_IIS;
		}

		//
		// filter the existent payload (if any)
		//
		
		if( pECB->cbAvailable > 0 )
		{
			code = contextPtr->rulesPtr->FilterEntityData(Buffer_T(pECB->lpbData, pECB->cbAvailable));
			if( Error_T::ERR_NONE != code )
			{
				goto FREE_MEM;
			}
		}

		//
		// Deliver to IIS if there is no more to be read
		//
		
		if( pECB->cbTotalBytes == pECB->cbAvailable )
		{
			goto SEND_IIS;
		}

		//
		// concatenate the data, as we'll need it
		// when we're done.
		//

		contextPtr->rawEntity.Concat(Buffer_T(pECB->lpbData, pECB->cbAvailable));

		//
		// Read another entity chunk (48K)
		//
		
		ReadAsync(pECB, contextPtr, 
			pECB->cbTotalBytes > sizeof(contextPtr->pbEntity) ? sizeof(contextPtr->pbEntity) : pECB->cbTotalBytes);

	}
	catch( Exception_T & exp )
	{
		dwErr = exp.Error;
		goto FREE_MEM;
	}
		

	//
	// Return pending and let the completion clean up.
	//
	
	return HSE_STATUS_PENDING;

FREE_MEM:

	if( contextPtr )
	{
		contextPtr->rulesPtr->Log(contextPtr->session, contextPtr->request, Error_T(code, dwErr));
		if( lstrlen(contextPtr->rulesPtr->szRejectFilename) > 0 )
		{
			contextPtr->Send404(pECB);
			return HSE_STATUS_SUCCESS_AND_KEEP_CONN;
		}
	}

	if( Error_T::ERR_WINDOWS == code )
	{
		Trace_T::Write(dwErr, __FILE__, __LINE__, __FUNCSIG__);
		SetLastError(dwErr);
		return HSE_STATUS_ERROR;
	}

SEND_IIS:

	return SendToIis(pECB, contextPtr);
}
